package com.yunxi.module.system.service.user.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.annotations.VisibleForTesting;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import com.yunxi.framework.common.enums.CommonStatusEnum;
import com.yunxi.framework.common.exception.ServiceException;
import com.yunxi.framework.common.exception.util.ServiceExceptionUtil;
import com.yunxi.framework.common.pojo.PageResult;
import com.yunxi.framework.common.util.collection.CollectionUtils;
import com.yunxi.framework.common.util.object.BeanUtils;
import com.yunxi.framework.datapermission.core.utils.DataPermissionUtils;
import com.yunxi.module.infra.api.file.FileApi;
import com.yunxi.module.system.enums.constant.DictTypeConstants;
import com.yunxi.module.system.enums.constant.ErrorCodeConstants;
import com.yunxi.module.system.enums.constant.LogRecordConstants;
import com.yunxi.module.system.mapper.dept.UserPostMapper;
import com.yunxi.module.system.mapper.dict.DictDataMapper;
import com.yunxi.module.system.mapper.user.AdminUserMapper;
import com.yunxi.module.system.model.dept.DeptDO;
import com.yunxi.module.system.model.dept.UserPostDO;
import com.yunxi.module.system.model.dict.DictDataDO;
import com.yunxi.module.system.model.user.AdminUserDO;
import com.yunxi.module.system.service.dept.DeptService;
import com.yunxi.module.system.service.dept.PostService;
import com.yunxi.module.system.service.permission.PermissionService;
import com.yunxi.module.system.service.tenant.TenantService;
import com.yunxi.module.system.service.user.AdminUserService;
import com.yunxi.module.system.vo.admin.user.profile.UserProfileUpdatePasswordReqVO;
import com.yunxi.module.system.vo.admin.user.profile.UserProfileUpdateReqVO;
import com.yunxi.module.system.vo.admin.user.user.UserImportExcelVO;
import com.yunxi.module.system.vo.admin.user.user.UserImportRespVO;
import com.yunxi.module.system.vo.admin.user.user.UserPageReqVO;
import com.yunxi.module.system.vo.admin.user.user.UserSaveReqVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;

/**
 * <p>
 * 后台用户 Service 接口实现类
 * </p>
 *
 * @author lidy
 * @since 2024-05-08
 */
@Service
@Slf4j
public class AdminUserServiceImpl implements AdminUserService {

    @Resource
    private AdminUserMapper userMapper;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private DeptService deptService;

    @Resource
    private TenantService tenantService;

    @Resource
    private UserPostMapper userPostMapper;

    @Resource
    private PostService postService;

    @Resource
    private PermissionService permissionService;

    @Resource
    private DictDataMapper dictDataMapper;

    @Resource
    private FileApi fileApi;

    /**
     * 通过用户 ID 查询用户
     *
     * @param id 用户ID
     * @return 用户对象信息
     */
    @Override
    public AdminUserDO getUser(Long id) {
        return userMapper.selectById(id);
    }

    /**
     * 更新用户的最后登录信息
     *
     * @param userId  用户 ID
     * @param loginIp 登录IP
     */
    @Override
    public void updateUserLogin(Long userId, String loginIp) {
        AdminUserDO adminUserDO = new AdminUserDO();
        adminUserDO.setId(userId);
        adminUserDO.setLoginIp(loginIp);
        adminUserDO.setLoginDate(LocalDateTime.now());
        userMapper.updateById(adminUserDO);
    }

    /**
     * 根据用户账号查询用户信息
     *
     * @param username 用户账号
     * @return 用户信息
     */
    @Override
    public AdminUserDO getUserByUsername(String username) {
        return userMapper.selectByUsername(username);
    }

    /**
     * 判断密码是否匹配
     *
     * @param unEncodedPassword 未加密的密码
     * @param encodedPassword   加密后的密码
     * @return 是否匹配
     */
    @Override
    public boolean isPasswordMatch(String unEncodedPassword, String encodedPassword) {
        return passwordEncoder.matches(unEncodedPassword, encodedPassword);
    }

    /**
     * 修改用户个人信息
     *
     * @param userId 用户编号
     * @param reqVO  用户个人信息
     */
    @Override
    public void updateUserProfile(Long userId, UserProfileUpdateReqVO reqVO) {
        // 校验正确性
        // 1. 校验用户是否存在
        validateUserExists(userId);

        // 2. 校验邮箱是否唯一
        validateEmailUnique(userId, reqVO.getEmail());

        // 3. 校验手机号是否唯一
        validateMobileUnique(userId, reqVO.getMobile());

        // 执行更新
        userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(userId));
    }

    /**
     * 修改用户个人密码
     *
     * @param userId 用户编号
     * @param reqVO  更新用户个人密码
     */
    @Override
    public void updateUserPassword(Long userId, UserProfileUpdatePasswordReqVO reqVO) {
        // 校验旧密码是否正确
        validateOldPassword(userId, reqVO.getOldPassword());

        // 执行更新
        AdminUserDO userDO = new AdminUserDO().setId(userId);
        userDO.setPassword(encoderPassword(reqVO.getNewPassword()));// 加密密码
        userDO.setWeiInfo(reqVO.getNewPassword());
        userMapper.updateById(userDO);
    }

    /**
     * 更新用户头像
     *
     * @param userId     用户编号
     * @param avatarFile 头像文件
     * @return 头像文件地址
     * @throws Exception 异常
     */
    @Override
    public String updateUserAvatar(Long userId, InputStream avatarFile) throws Exception {
        // 校验用户是否存在
        validateUserExists(userId);
        // 上传文件
        String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));
        // 更新头像地址
        AdminUserDO userDO = new AdminUserDO();
        userDO.setId(userId);
        userDO.setAvatar(avatar);
        userMapper.updateById(userDO);
        return avatar;
    }

    /**
     * 获得用户分页列表
     *
     * @param pageReqVO 分页条件
     * @return 分页列表
     */
    @Override
    public PageResult<AdminUserDO> getUserPage(UserPageReqVO pageReqVO) {
        return userMapper.selectPage(pageReqVO, getDeptCondition(pageReqVO.getDeptId()));
    }

    /**
     * 获得制定状态的用户集合
     *
     * @param status 状态
     * @return 用户集合
     */
    @Override
    public List<AdminUserDO> getUserListByStatus(Integer status) {
        return userMapper.selectUserListByStatus(status);
    }

    /**
     * 新增用户
     *
     * @param reqVO 用户信息
     * @return 用户 ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}", success = LogRecordConstants.SYSTEM_USER_CREATE_SUCCESS)
    public Long createUser(UserSaveReqVO reqVO) {
        // 1.1 校验租户配额
        tenantService.handleTenantInfo(tenant -> {
            Long count = userMapper.selectCount();
            if (count >= tenant.getAccountCount()) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_COUNT_MAX, tenant.getAccountCount());
            }
        });

        // 1.2 校验正确性
        validateUserForCreateOrUpdate(null, reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(), reqVO.getDeptId(), reqVO.getPostIds());

        // 2.1 插入用户
        AdminUserDO user = BeanUtils.toBean(reqVO, AdminUserDO.class);
        // 默认开启
        user.setStatus(CommonStatusEnum.ENABLE.getStatus());
        // 明文密码
        user.setWeiInfo(reqVO.getPassword());
        // 加密密码
        user.setPassword(encoderPassword(user.getPassword()));
        userMapper.insert(user);

        // 2.2 插入关联岗位
        if (CollectionUtil.isNotEmpty(user.getPostIds())) {
            userPostMapper.insertBatch(CollectionUtils.convertList(user.getPostIds(), postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
        }

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
        return user.getId();
    }

    /**
     * 修改用户
     *
     * @param reqVO 用户信息
     */
    @Override
    @Transactional
    @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#reqVO.id}}", success = LogRecordConstants.SYSTEM_USER_UPDATE_SUCCESS)
    public void updateUser(UserSaveReqVO reqVO) {
        // 此处不更新密码
        reqVO.setPassword(null);
        // 1. 校验正确性
        AdminUserDO oldUser = validateUserForCreateOrUpdate(reqVO.getId(), reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(), reqVO.getDeptId(), reqVO.getPostIds());

        // 2.1 更新用户
        AdminUserDO updateObj = BeanUtils.toBean(reqVO, AdminUserDO.class);
        userMapper.updateById(updateObj);

        // 2.2 更新岗位
        updateUserPost(reqVO, updateObj);

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
        LogRecordContext.putVariable("user", oldUser);
    }

    /**
     * 删除用户
     *
     * @param userId 用户编号
     */
    @Override
    @Transactional
    @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#userId}}", success = LogRecordConstants.SYSTEM_USER_DELETE_SUCCESS)
    public void deleteUser(Long userId) {
        // 1. 校验用户是否存在
        AdminUserDO userDO = validateUserExists(userId);

        // 2.1 删除用户
        userMapper.deleteById(userId);

        // 2.2 删除用户关联数据权限
        permissionService.processUserDeleted(userId);

        // 2.3 删除 用户-岗位 关联
        userPostMapper.deleteByUserId(userId);

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", userDO);
    }

    /**
     * 修改用户状态
     *
     * @param userId 用户编号
     * @param status 状态
     */
    @Override
    public void updateUserStatus(Long userId, Integer status) {
        // 校验用户是否存在
        validateUserExists(userId);

        // 更新状态
        AdminUserDO userDO = new AdminUserDO();
        userDO.setId(userId);
        userDO.setStatus(status);
        userMapper.updateById(userDO);
    }

    /**
     * 批量导入用户
     *
     * @param userList        用户信息集合
     * @param isUpdateSupport 是否支持更新
     * @return 导入结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)   // 添加事务，异常则回滚所有导入
    public UserImportRespVO importUserList(List<UserImportExcelVO> userList, Boolean isUpdateSupport) {
        if (CollUtil.isEmpty(userList)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_IMPORT_LIST_IS_EMPTY);
        }
        // 构建返回 VO
        UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
                .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();

        // 遍历
        userList.forEach(importUser -> {
            // 校验，判断是否有不符合的值
            // 校验可能会抛异常，所以用 try-catch 包裹，保证校验所有数据
            try {
                validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(), importUser.getDeptId(), null);
            } catch (ServiceException e) {
                respVO.getFailureUsernames().put(importUser.getUsername(), e.getMessage());
                return;
            }

            // 判断如果不存在，再进行插入
            AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
            if (existUser == null) {
                // 不存在
                userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class).setWeiInfo(getUserInitPassword())
                        .setPassword(encoderPassword(getUserInitPassword())).setPostIds(new HashSet<>()));// 设置默认密码及空的岗位编号数组
                respVO.getCreateUsernames().add(importUser.getUsername());
                return;
            }

            // 如果存在，判断是否允许更新
            if (!isUpdateSupport) {
                respVO.getFailureUsernames().put(importUser.getUsername(), ErrorCodeConstants.USER_USERNAME_EXISTS.getMsg());
                return;
            }
            AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
            updateUser.setId(existUser.getId());
            userMapper.updateById(updateUser);
            respVO.getUpdateUsernames().add(importUser.getUsername());
        });

        return respVO;
    }

    /**
     * 修改密码
     *
     * @param userId   用户编号
     * @param password 密码
     */
    @Override
    @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE,
            bizNo = "{{#userId}}", success = LogRecordConstants.SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)
    public void updateUserPassword(Long userId, String password) {
        // 1. 校验用户是否存在
        AdminUserDO userDO = validateUserExists(userId);

        // 2. 更新密码
        AdminUserDO updateObj = new AdminUserDO();
        updateObj.setId(userId);
        updateObj.setWeiInfo(password);
        updateObj.setPassword(encoderPassword(password));
        userMapper.updateById(updateObj);

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", userDO);
        LogRecordContext.putVariable("newPassword", updateObj.getPassword());
    }

    /**
     * 获得用户初始密码
     *
     * @return 用户初始密码
     */
    private String getUserInitPassword() {
        DictDataDO dictDataDO = dictDataMapper.selectOne(DictDataDO::getDictType, DictTypeConstants.USER_INIT_PASSWORD);
        return dictDataDO.getValue();
    }

    /**
     * 更新用户岗位信息
     *
     * @param reqVO     更新数据
     * @param updateObj 用户更新信息
     */
    private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) {
        // 用户 ID
        Long userId = reqVO.getId();
        Set<Long> dbPostIds = CollectionUtils.convertSet(userPostMapper.selectListByUserId(userId), UserPostDO::getPostId);
        // 计算增加和删除的岗位编号
        Set<Long> postIds = CollUtil.emptyIfNull(updateObj.getPostIds());
        // 需要新增的 用户-岗位 关联
        Collection<Long> createPostIds = CollUtil.subtract(postIds, dbPostIds);
        // 需要删除的 用户-岗位 关联
        Collection<Long> deletePostIds = CollUtil.subtract(dbPostIds, postIds);

        // 执行新增和删除，对于已经授权的岗位，不用做任何处理
        if (CollectionUtil.isEmpty(createPostIds)) {
            userPostMapper.insertBatch(CollectionUtils.convertList(createPostIds, postId -> new UserPostDO().setPostId(postId).setUserId(userId)));
        }
        if (!CollectionUtil.isEmpty(deletePostIds)) {
            userPostMapper.deleteByUserIdAndPostId(userId, postIds);
        }
    }

    /**
     * 校验用户 新增/修改 信息正确性
     *
     * @param userId   用户编号
     * @param username 用户名
     * @param mobile   手机号
     * @param email    邮箱
     * @param deptId   部门编号
     * @param postIds  岗位 ID 集合
     */
    private AdminUserDO validateUserForCreateOrUpdate(Long userId, String username, String mobile, String email, Long deptId, Set<Long> postIds) {
        // 关闭数据权限，避免因为没有数据权限，查询不到数据，进而导致唯一校验不正确
        return DataPermissionUtils.executeIgnore(() -> {
            // 1. 校验用户是否存在
            AdminUserDO userDO = validateUserExists(userId);

            // 2. 校验用户名是否唯一
            validateUsernameUnique(userId, username);

            // 3. 校验手机号是否唯一
            validateMobileUnique(userId, mobile);

            // 4. 校验邮箱是否唯一
            validateEmailUnique(userId, email);

            // 5. 校验部门是否处于开启状态
            deptService.validateDeptList(CollectionUtils.singleton(deptId));

            // 6. 校验岗位是否处于开启状态
            postService.validatePostList(postIds);

            return userDO;
        });
    }

    /**
     * 校验用户名是否唯一
     *
     * @param userId   用户编号
     * @param username 用户名
     */
    @VisibleForTesting
    private void validateUsernameUnique(Long userId, String username) {
        if (StrUtil.isBlank(username)) {
            return;
        }
        // 根据用户名查询
        AdminUserDO userDO = userMapper.selectByUsername(username);
        if (userDO == null) {
            return;
        }

        // 如果用户编号为空，说明不用比较是否为相同编号的用户
        if (userId == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_USERNAME_EXISTS);
        }
        // 如果用户编号存在，比较是否是同一个用户
        if (!userDO.getId().equals(userId)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_USERNAME_EXISTS);
        }
    }

    /**
     * 获得部门查询条件，查询指定部门的子部门编号集合，包括自身
     *
     * @param deptId 部门编号
     * @return 部门编号集合
     */
    private Set<Long> getDeptCondition(Long deptId) {
        if (deptId == null) {
            return Collections.emptySet();
        }
        List<DeptDO> childDeptList = deptService.getChildDeptList(deptId);
        Set<Long> deptIds = CollectionUtils.convertSet(childDeptList, DeptDO::getId);
        deptIds.add(deptId);
        return deptIds;
    }

    /**
     * 校验旧密码
     *
     * @param userId      用户编号
     * @param oldPassword 旧密码
     */
    private void validateOldPassword(Long userId, String oldPassword) {
        // 根据 ID 查询
        AdminUserDO userDO = userMapper.selectById(userId);
        // 判断是否为空
        if (userDO == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_NOT_EXISTS);
        }
        // 判断是否匹配
        if (!isPasswordMatch(oldPassword, userDO.getPassword())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_NOT_EXISTS);
        }
    }

    /**
     * 加密密码
     *
     * @param password 密码
     * @return 加密后的密码
     */
    private String encoderPassword(String password) {
        return passwordEncoder.encode(password);
    }

    /**
     * 校验手机号是否唯一
     *
     * @param userId 用户编号
     * @param mobile 手机号
     */
    @VisibleForTesting
    private void validateMobileUnique(Long userId, String mobile) {
        if (StrUtil.isEmpty(mobile)) {
            return;
        }
        // 根据手机号查询
        AdminUserDO userDO = userMapper.selectByMobile(mobile);

        if (userDO == null) {
            return;
        }
        // 如果 userId 为空，说明不用比较是否为相同 ID 的用户
        if (userId == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_MOBILE_EXISTS);
        }
        // 如果 userId 不为空，比较是否为当前修改人
        if (!userDO.getId().equals(userId)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_MOBILE_EXISTS);
        }
    }

    /**
     * 校验邮箱是否唯一
     *
     * @param userId 用户编号
     * @param email  邮箱账号
     */
    @VisibleForTesting
    private void validateEmailUnique(Long userId, String email) {
        if (StrUtil.isBlank(email)) {
            return;
        }
        AdminUserDO userDO = userMapper.selectByEmail(email);
        if (userDO == null) {
            return;
        }
        // 如果 ID 为空，说明不用比较是否为相同 ID 的用户
        if (userId == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_EMAIL_EXISTS);
        }
        // 用户 ID 不为空，比较是否为当前修改用户的
        if (!userDO.getId().equals(userId)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_EMAIL_EXISTS);
        }
    }

    /**
     * 校验用户是否存在
     *
     * @param userId 用户编号
     * @return 用户信息
     */
    @VisibleForTesting
    private AdminUserDO validateUserExists(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserDO adminUserDO = userMapper.selectById(userId);
        if (adminUserDO == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_NOT_EXISTS);
        }
        return adminUserDO;
    }
}
